package com.eva.job;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.eva.dao.system.model.SystemJob;
import com.eva.dao.system.model.SystemJobLog;
import com.eva.framework.common.exception.LockedException;
import com.eva.framework.common.utils.Utils;
import com.eva.job.constants.JobConstants;
import com.eva.job.dto.TriggerJobDTO;
import com.eva.service.system.SystemJobLogService;
import com.eva.service.system.SystemJobService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Date;

@Slf4j
@Service
public class SystemJobTrigger {

    @Resource
    private SystemJobLogService systemJobLogService;

    @Resource
    private SystemJobService systemJobService;

    /**
     * 触发定时任务
     *
     * @param dto 触发参数
     */
    public void trigger(TriggerJobDTO dto) {
        SystemJob job = null;
        JobParam jobParam = new JobParam();
        boolean locked = false;
        SystemJobLog systemJobLog = new SystemJobLog();
        try {
            Integer jobId = dto.getId();
            job = systemJobService.findById(jobId);
            // 验证JOB
            if (!this.checkJob(job)) {
                trace(job, "任务验证未通过");
                return;
            }
            // 初始化JOB数据
            trace(job, "开始执行定时任务");
            trace(job, "任务线程名称: {}", Thread.currentThread().getName());
            trace(job, "任务执行时间点: {}", Utils.Date.format(dto.getBusinessTime()));
            jobParam.setId(jobId);
            jobParam.setTriggerType(dto.getTriggerType());
            jobParam.setBusinessTime(dto.getBusinessTime());
            // 记录日志
            if (job.getWithLog()) {
                systemJobLog.setJobId(jobId);
                systemJobLog.setJobName(job.getJobName());
                systemJobLog.setRunTimeStart(new Date());
                systemJobLog.setServerIp(Utils.Server.getIP());
                systemJobLog.setBusinessTime(dto.getBusinessTime());
                systemJobLog.setStatus(JobConstants.LogStatus.NONE.getCode());
                systemJobLog.setTriggerType(dto.getTriggerType());
                systemJobLog.setRemark("任务正在执行中");
                systemJobLogService.create(systemJobLog);
            }
            // 任务加锁，防止多服务器重复执行
            this.lock(job);
            locked = true;
            // 不存在分片，直接执行任务（根据任务处理器名称获取任务处理器实例并执行）
            BaseJob jobBean = Utils.SpringContext.getBean(job.getHandler(), BaseJob.class);
            JobContext jobContext = jobBean.execute(jobParam);
            // 解锁任务
            this.unlock(job, dto.getNextFireTime());
            locked = false;
            // 修改日志信息
            if (job.getWithLog()) {
                systemJobLog.setRemark("任务执行完成");
                systemJobLog.setHandleTotalSize(jobContext.getHandleTotalSize());
                systemJobLog.setHandleSuccessSize(jobContext.getHandleSuccessSize());
                systemJobLog.setContext(jobContext.getContext());
                systemJobLog.setStatus(JobConstants.LogStatus.SUCCESS.getCode());
            }
        } catch (LockedException e) {
            trace(job, e.getMessage());
            // 修改日志状态
            if (job.getWithLog()) {
                systemJobLog.setStatus(JobConstants.LogStatus.IGNORE.getCode());
                systemJobLog.setRemark("任务正在被其他服务执行或锁定");
            }
        } catch (NoSuchBeanDefinitionException e) {
            if (job == null) {
                e.printStackTrace();
                return;
            }
            trace(job, "找不到任务处理器" + job.getHandler());
            // 修改日志状态
            if (job.getWithLog()) {
                systemJobLog.setStatus(JobConstants.LogStatus.IGNORE.getCode());
                systemJobLog.setRemark("找不到任务处理器" + job.getHandler());
            }
        } catch (Exception e) {
            if (job == null) {
                e.printStackTrace();
                return;
            }
            error(job, e);
            // 修改日志状态
            if (job.getWithLog()) {
                systemJobLog.setStatus(JobConstants.LogStatus.FAILED.getCode());
                systemJobLog.setRemark("任务执行失败，出现异常：" + e.getMessage());
            }
        } finally {
            // 更新日志
            if (job != null && job.getWithLog() && systemJobLog.getId() != null) {
                systemJobLog.setRunTimeEnd(new Date());
                systemJobLog.setRunSpendTime(Integer.valueOf(String.valueOf(systemJobLog.getRunTimeEnd().getTime() - systemJobLog.getRunTimeStart().getTime())));
                systemJobLogService.updateById(systemJobLog);
            }
            // 任务解锁
            if (locked) {
                this.unlock(job, dto.getNextFireTime());
                trace(job, "定时任务执行完毕");
            }
        }
    }

    /**
     * 锁定JOB
     *
     * @param job JOB
     */
    private void lock (SystemJob job) throws LockedException{
        trace(job, "开始锁定定时任务");
        if (job.getStatus().equals(JobConstants.JobStatus.RUNNING.getCode())) {
            throw new LockedException("锁定失败，定时任务[" + job.getJobName() + "]正在执行");
        }
        UpdateWrapper<SystemJob> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda()
                .eq(SystemJob::getId, job.getId())
                .eq(SystemJob::getLockNumber, job.getLockNumber())
                .set(SystemJob::getStatus, JobConstants.JobStatus.RUNNING.getCode())
                .set(SystemJob::getLockNumber, job.getLockNumber() + 1);
        int result = systemJobService.update(updateWrapper);
        if (result == 0) {
            throw new LockedException("锁定失败，定时任务[" + job.getJobName() + "]已被锁定");
        }
        trace(job, "锁定定时任务成功");
    }

    /**
     * 解锁JOB
     *
     * @param job JOB
     */
    private void unlock(SystemJob job, Date nextFireTime) {
        trace(job, "解锁定时任务");
        UpdateWrapper<SystemJob> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda()
                .eq(SystemJob::getId, job.getId())
                .set(SystemJob::getStatus, JobConstants.JobStatus.READY.getCode())
                .set(SystemJob::getNextFireTime, nextFireTime);
        systemJobService.update(updateWrapper);
        trace(job, "解锁定时任务成功");
    }

    /**
     * 验证Job是否可执行
     *
     * @param job JOB
     */
    private boolean checkJob (SystemJob job) {
        // 定时任务已被删除
        if (job == null) {
            log.warn("找不到定时任务");
            return Boolean.FALSE;
        }
        // 白名单验证
        if (StringUtils.isNotBlank(job.getAllowServerIps())) {
            if (!Arrays.asList(job.getAllowServerIps().split(",")).contains(Utils.Server.getIP())) {
                warn(job, "当前IP {} 不在任务服务器白名单中，取消任务执行！", Utils.Server.getIP());
                return Boolean.FALSE;
            }
        }
        return Boolean.TRUE;
    }

    /**
     * 打印跟踪日志
     *
     * @param job JOB
     * @param text 日志模版内容
     * @param params 日志参数
     */
    private void trace (SystemJob job, String text, Object... params) {
        log.trace("{}: " + text, String.format("%s_%s", job.getJobName(), job.getHandler()), params);
    }

    /**
     * 打印警告日志
     *
     * @param job JOB
     * @param text 日志模版内容
     * @param params 日志参数
     */
    private void warn (SystemJob job, String text, Object... params) {
        log.warn("{}: " + text, String.format("%s_%s", job.getJobName(), job.getHandler()), params);
    }

    /**
     * 打印错误日志
     *
     * @param job JOB
     * @param e 异常对象
     */
    private void error (SystemJob job, Exception e) {
        log.error(String.format("%s_%s", job.getJobName(), job.getHandler()) + ": 执行任务时抛出了异常", e);
    }
}
